﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CsClient
{
    static class ZnanaMapa
    {
        static private HashSet<PoleMapy> zbiorPol = new HashSet<PoleMapy>();
        static private int minX = 0;
        static private int maxX = 0;
        static private int minY = 0;
        static private int maxY = 0;
        static public Wspolrzedne najblizszaNieskonczonaEnergia = null;
        static public int kosztDoNieskonczonejEnergii = -1;
        static public Wspolrzedne najblizszaEnergia = null;
        static public int kosztDoEnergii = -1;

        #region Dodawanie i uaktualnianie znanych agentowi pól.

        static public void dodajPole(Wspolrzedne polozenie, int wysokoscPola, int iloscEnergii,
            bool czyJestPrzeszkoda)
        {
            zbiorPol.Add(new PoleMapy(polozenie, wysokoscPola, iloscEnergii, czyJestPrzeszkoda,
                znajdzPole(polozenie.getX(), polozenie.getY() + 1), znajdzPole(polozenie.getX()
                + 1, polozenie.getY()), znajdzPole(polozenie.getX(), polozenie.getY() - 1),
                znajdzPole(polozenie.getX() - 1, polozenie.getY())));
            if (polozenie.getX() < minX)
                minX = polozenie.getX();
            if (polozenie.getX() > maxX)
                maxX = polozenie.getX();
            if (polozenie.getY() < minY)
                minY = polozenie.getY();
            if (polozenie.getY() > maxY)
                maxY = polozenie.getY();
        }

        static public void uaktualnijPole(Wspolrzedne polozenie, int wysokoscPola, int iloscEnergii,
            bool czyJestPrzeszkoda)
        {
            if (czyZnamPole(polozenie.getX(), polozenie.getY()))
            {
                znajdzPole(polozenie.getX(), polozenie.getY()).setEnergia(iloscEnergii);
            }
            else
            {
                dodajPole(polozenie, wysokoscPola, iloscEnergii, czyJestPrzeszkoda);
            }
        }

        static public void pobierzEnergie(Wspolrzedne polozenie, int pobranaEnergia)
        {
            znajdzPole(polozenie).pobranoEnergie(pobranaEnergia);
        }

        #endregion

        #region Różne sposoby zapisu mapy

        // Wyświetla znane agentowy pola w postaci czytelnej dla człowieka.
        static public void show()
        {
            Console.Write("\n");
            for (int i = maxY + 1; i >= minY - 1; i--)
            {
                for (int j = minX - 1; j <= maxX + 1; j++)
                {
                    PoleMapy wyswietlane = znajdzPole(j, i);
                    if (wyswietlane == null)
                    {
                        Console.Write("?");
                    }
                    /*
                    else if (wyswietlane.getUnrechable())
                    {
                        Console.Write("@");
                    }
                     */
                    else if (wyswietlane.czyJestPrzeszkoda())
                    {
                        Console.Write("X");
                    }
                    else
                    {
                        if ((Agent.getPozycjaAgenta().getX() == j) && (Agent.getPozycjaAgenta()
                            .getY() == i))
                        {
                            switch (Agent.getKierunekPatrzenia())
                            {
                                case 0:
                                    Console.Write("A");
                                    break;
                                case 1:
                                    Console.Write(">");
                                    break;
                                case 2:
                                    Console.Write("V");
                                    break;
                                case 3:
                                    Console.Write("<");
                                    break;
                            }
                        }
                        else if (wyswietlane.getEnergia() == 0)
                        {
                            Console.Write(" ");
                        }
                        else
                            Console.Write("E");
                    }
                }
                Console.Write("\n");
            }
            Console.Write("\n");
        }

        static public String toString()
        {
            Wspolrzedne relPosRozmowcy = Komunikacja.pozycjaRozmowcy;
            int wysokoscRozmowcy = Komunikacja.wysokoscRozmowcy;
            int kierPatrzRozmowcy = Komunikacja.kierunekPatrzeniaRozmowcy;
            String dlugiCiag = "_";
            switch(kierPatrzRozmowcy){
                case 0:
                    foreach (PoleMapy p in zbiorPol)
                    {
                        String nastepne = "";
                        nastepne += (p.getPolozenie().getX() - relPosRozmowcy.getX());
                        nastepne += "." + (p.getPolozenie().getY() - relPosRozmowcy.getY());
                        nastepne += "." + (wysokoscRozmowcy - p.getWysokosc());
                        nastepne += "." + p.getEnergia() + ".";
                        if (p.czyJestPrzeszkoda())
                            nastepne += "T ";
                        else
                            nastepne += "N ";
                        if (dlugiCiag.Length + nastepne.Length < 974)
                            dlugiCiag += nastepne;
                    }
                    break;
                case 1:
                    foreach (PoleMapy p in zbiorPol)
                    {
                        String nastepne = "";
                        nastepne += (relPosRozmowcy.getY() - p.getPolozenie().getY());
                        nastepne += "." + (p.getPolozenie().getX() - relPosRozmowcy.getX());
                        nastepne += "." + (wysokoscRozmowcy - p.getWysokosc());
                        nastepne += "." + p.getEnergia() + ".";
                        if (p.czyJestPrzeszkoda())
                            nastepne += "T ";
                        else
                            nastepne += "N ";
                        if (dlugiCiag.Length + nastepne.Length < 974)
                            dlugiCiag += nastepne;
                    }
                    break;
                case 2:
                    foreach (PoleMapy p in zbiorPol)
                    {
                        String nastepne = "";
                        nastepne += (relPosRozmowcy.getX() - p.getPolozenie().getX());
                        nastepne += "." + (relPosRozmowcy.getY() - p.getPolozenie().getY());
                        nastepne += "." + (wysokoscRozmowcy - p.getWysokosc());
                        nastepne += "." + p.getEnergia() + ".";
                        if (p.czyJestPrzeszkoda())
                            nastepne += "T ";
                        else
                            nastepne += "N ";
                        if (dlugiCiag.Length + nastepne.Length < 974)
                            dlugiCiag += nastepne;
                    }
                    break;
                case 3:
                    foreach (PoleMapy p in zbiorPol)
                    {
                        String nastepne = "";
                        nastepne += (p.getPolozenie().getY() - relPosRozmowcy.getY());
                        nastepne += "." + (relPosRozmowcy.getX() - p.getPolozenie().getX());
                        nastepne += "." + (wysokoscRozmowcy - p.getWysokosc());
                        nastepne += "." + p.getEnergia() + ".";
                        if (p.czyJestPrzeszkoda())
                            nastepne += "T ";
                        else
                            nastepne += "N ";
                        if (dlugiCiag.Length + nastepne.Length < 974)
                            dlugiCiag += nastepne;
                    }
                    break;
            }
            return dlugiCiag;
        }

        static public void przyjmijMapeZeStringa(String mapaWStringu)
        {
            List<String> polaWStringach = new List<string>(mapaWStringu.Split(' '));
            foreach (String pole in polaWStringach)
            {
                if (pole.Length > 8)
                {
                    List<String> polaPol = new List<string>(pole.Split('.'));
                    int x = int.Parse(polaPol[0]);
                    int y = int.Parse(polaPol[1]);
                    if (!czyZnamPole(x, y))
                    {
                        int wysokosc = int.Parse(polaPol[2]);
                        int energia = int.Parse(polaPol[3]);
                        bool przeszkoda = false;
                        if (polaPol[4] == "T")
                            przeszkoda = true;
                        uaktualnijPole(new Wspolrzedne(x, y), wysokosc, energia, przeszkoda);
                    }
                }
            }

            znajdzNajblizszaEnergie();
        }

        #endregion

        #region Metody pomocnicze

        // Zwraca Pole o podanych współrzędnych.
        // Zwraca null w razie nieznanego mu pola.
        static public PoleMapy znajdzPole(int x, int y)
        {
            foreach (PoleMapy pole in zbiorPol)
            {
                if (Wspolrzedne.toSamoPole(pole.getPolozenie(), new Wspolrzedne(x, y)))
                    return pole;
            }
            return null;
        }

        static public PoleMapy znajdzPole(Wspolrzedne pozycja)
        {
            return znajdzPole(pozycja.getX(), pozycja.getY());
        }

        // Sprawdza, czy dane pole istnieje w zbiorze znanych agentowi pól.
        static private bool czyZnamPole(int x, int y)
        {
            foreach (PoleMapy pole in zbiorPol)
            {
                if ((pole.getPolozenie().getX() == x) && (pole.getPolozenie().getY() == y))
                    return true;
            }
            return false;
        }

        static public List<PoleMapy> getSasiadujacePola(PoleMapy srodek)
        {
            List<PoleMapy> zwracanaLista = new List<PoleMapy>();

            zwracanaLista.Add(znajdzPole(srodek.getPolozenie().getX(), srodek.getPolozenie().getY() + 1));
            zwracanaLista.Add(znajdzPole(srodek.getPolozenie().getX() + 1, srodek.getPolozenie().getY()));
            zwracanaLista.Add(znajdzPole(srodek.getPolozenie().getX(), srodek.getPolozenie().getY() - 1));
            zwracanaLista.Add(znajdzPole(srodek.getPolozenie().getX() - 1, srodek.getPolozenie().getY()));

            return zwracanaLista;
        }

        static public Wspolrzedne znajdzNajblizszaEnergie()
        {
            int smiec;
            return znajdzNajblizszaEnergie(out smiec);
        }

        static public Wspolrzedne znajdzNajblizszaEnergie(out int energiaPola)
        {
            energiaPola = 0;
            List<PoleMapy> listaPolZEnergia = new List<PoleMapy>();
            foreach (PoleMapy pm in zbiorPol)
            {
                if (pm.getEnergia() != 0)
                    listaPolZEnergia.Add(pm);
            }
            if (listaPolZEnergia.Count == 0)
            {
                najblizszaEnergia = null;
                kosztDoEnergii = -1;
                return najblizszaEnergia;
            }
            int minKoszt = 100000;
            PoleMapy najblizsze = null;
            int minInfKoszt = 100000;
            PoleMapy najblizszeInf = null;
            foreach (PoleMapy poleEnergii in listaPolZEnergia)
            {
                int[] najkrotsza = znajdzNajtanszaDroge(poleEnergii.getPolozenie());
                if (najkrotsza != null)
                {
                    int koszt = najkrotsza[0];
                    if (koszt < minKoszt)
                    {
                        minKoszt = koszt;
                        najblizsze = poleEnergii;
                    }
                    if (poleEnergii.getEnergia() < 0)
                    {
                        if (koszt < minInfKoszt)
                        {
                            minInfKoszt = koszt;
                            najblizszeInf = poleEnergii;
                        }
                    }
                }
            }
            if (najblizsze == null)
            {
                najblizszaEnergia = null;
                kosztDoEnergii = -1;
                return najblizszaEnergia;
            }
            najblizszaEnergia = najblizsze.getPolozenie();
            kosztDoEnergii = minKoszt;
            if (najblizszeInf != null)
            {
                najblizszaNieskonczonaEnergia = najblizszeInf.getPolozenie();
                kosztDoNieskonczonejEnergii = minInfKoszt;
            }
            return najblizsze.getPolozenie();
        }

        #endregion

        #region Wyszukiwanie ścieżek

        static public Wspolrzedne najblizszeUkrytePole()
        {
            int zbedna;
            return najblizszeUkrytePole(out zbedna);
        }

        //Zmienna pierwszyKrok zwraca kierunek pierwszego kroku.
        //-1-NIE DA SIĘ 0-pn 1-ws 2-pd 3-za
        static public Wspolrzedne najblizszeUkrytePole(out int pierwszyKrok)
        {
            int mojaPozycjaX = Agent.getPozycjaAgenta().getX();
            int mojaPozycjaY = Agent.getPozycjaAgenta().getY();
            for (int i = 1; ; i++)
            {
                int lTestowanych = 0;
                int lNieznanych = 0;
                List<Wspolrzedne> potencjalneCele = new List<Wspolrzedne>();
                for (int j = mojaPozycjaY + i; j >= mojaPozycjaY - i; j--)
                {
                    for (int k = mojaPozycjaX - i; k <= mojaPozycjaX + i; k++)
                    {
                        if ((mojaPozycjaY + i > j) && (mojaPozycjaY - i < j)
                            && (mojaPozycjaX - i < k) && (mojaPozycjaX + i > k))
                        {
                            goto Nastepne;
                        }

                        PoleMapy testowane = znajdzPole(k, j);
                        lTestowanych++;
                        if (testowane != null)
                        {
                            if (testowane.czySasiadujeZNieznanym())
                                potencjalneCele.Add(new Wspolrzedne(k, j));
                        }
                        else
                        {
                            lNieznanych++;
                        }

                        Nastepne:
                        ;
                    }
                }
                if (lNieznanych == lTestowanych)
                {
                    pierwszyKrok = -1;
                    return null;
                }
                if (potencjalneCele.Count > 0)
                {
                    Wspolrzedne closest = null;
                    int shortestWay = 2 * Program.cennikSwiata.initialEnergy;
                    pierwszyKrok = -1;
                    foreach (Wspolrzedne wsp in potencjalneCele)
                    {
                        int[] comparer = znajdzNajtanszaDroge(wsp);
                        if (comparer != null)
                        {
                            if (shortestWay > comparer[0])
                            {
                                closest = wsp;
                                shortestWay = comparer[0];
                                pierwszyKrok = comparer[1];
                            }
                        }
                    }
                    if(closest != null)
                        return closest;
                }
            }
        }

        //Heurystyczne oszacowanie odległości do celu.
        private static int hKoszt(PoleMapy p, Wspolrzedne cel)
        {
            int poleX = p.getPolozenie().getX();
            int poleY = p.getPolozenie().getY();
            int celX = cel.getX();
            int celY = cel.getY();
            int hKoszt = 0;

            hKoszt += Program.cennikSwiata.rotateCost;
            int poX = Math.Abs(poleX - celX);
            int poY = Math.Abs(poleY - celY);
            hKoszt += (int)((poX + poY) * Program.cennikSwiata.moveCost);

            return hKoszt;
        }

        //Znajdowanie najtańszej drogi z pomocą algorytmu A*
        //Zwraca: koszt drogi, kierunek piewszego kroku
        //0-pn, 1-ws 2-pd 3-za
        static public int[] znajdzNajtanszaDroge(Wspolrzedne cel)
        {
            int mojaPozycjaX = Agent.getPozycjaAgenta().getX();
            int mojaPozycjaY = Agent.getPozycjaAgenta().getY();
            HashSet<PoleMapy> przejrzanePola = new HashSet<PoleMapy>();
            List<PoleMapy> doRozpatrzeniaPola = new List<PoleMapy>();
            PoleMapy pierwsze = znajdzPole(mojaPozycjaX, mojaPozycjaY);
            doRozpatrzeniaPola.Add(pierwsze);

            if (Wspolrzedne.toSamoPole(pierwsze.getPolozenie(), cel))
            {
                int[] zwracane = new int[2];
                zwracane[0] = 0;
                zwracane[1] = -1;
                return zwracane;
            }

            Dictionary<PoleMapy, PoleMapy> came_from = new Dictionary<PoleMapy, PoleMapy>();

            Dictionary<PoleMapy, int> g_score = new Dictionary<PoleMapy,int>();
            Dictionary<PoleMapy, int> h_score = new Dictionary<PoleMapy,int>();
            Dictionary<PoleMapy, int> f_score = new Dictionary<PoleMapy,int>();

            g_score[pierwsze] = 0;
            h_score[pierwsze] = hKoszt(pierwsze, cel);
            f_score[pierwsze] = g_score[pierwsze] + h_score[pierwsze];

            while (doRozpatrzeniaPola.Count > 0)
            {
                PoleMapy x = null;
                foreach (PoleMapy p in doRozpatrzeniaPola)
                {
                    if (x == null)
                    {
                        x = p;
                    }
                    else if (f_score[p] < f_score[x])
                        x = p;
                }

                #region Kierunek uzyskany po poprzednim kroku

                int z = -1;
                if (x == pierwsze)
                    z = Agent.getKierunekPatrzenia();
                else
                {
                    if (came_from[x].getPolnocne() != null)
                    {
                        if (came_from[x].getPolnocne().getDokad() == x)
                            z = 0;
                    }
                    if (came_from[x].getWschodnie() != null)
                    {
                        if (came_from[x].getWschodnie().getDokad() == x)
                            z = 1;
                    }
                    if (came_from[x].getPoludniowe() != null)
                    {
                        if (came_from[x].getPoludniowe().getDokad() == x)
                            z = 2;
                    }
                    if (came_from[x].getZachodnie() != null)
                    {
                        if (came_from[x].getZachodnie().getDokad() == x)
                            z = 3;
                    }
                }

                #endregion

                #region Doszlismy do celu

                if (Wspolrzedne.toSamoPole(x.getPolozenie(), cel))
                {
                    int[] zwrot = new int[2];
                    PoleMapy o = znajdzPierwszyKrok(came_from, x);
                    zwrot[0] = g_score[x];
                    if (pierwsze.getPolnocne() != null)
                    {
                        if (pierwsze.getPolnocne().getDokad() == o)
                            z = 0;
                    }
                    if (pierwsze.getWschodnie() != null)
                    {
                        if (pierwsze.getWschodnie().getDokad() == o)
                            z = 1;
                    }
                    if (pierwsze.getPoludniowe() != null)
                    {
                        if (pierwsze.getPoludniowe().getDokad() == o)
                            z = 2;
                    }
                    if (pierwsze.getZachodnie() != null)
                    {
                        if (pierwsze.getZachodnie().getDokad() == o)
                            z = 3;
                    }
                    zwrot[1] = z;
                    return zwrot;
                }

                #endregion

                doRozpatrzeniaPola.Remove(x);
                przejrzanePola.Add(x);

                foreach(PoleMapy y in getSasiadujacePola(x))
                {
                    if (y == null)
                        goto KoniecForeacha;
                    if (y.czyJestPrzeszkoda())
                        goto KoniecForeacha;
                    if (przejrzanePola.Contains(y))
                        goto KoniecForeacha;
                    int niepewny_g_score = g_score[x];

                    #region Kierunek obecnego kroku

                    int a = -1;
                    if (x.getPolnocne() != null)
                    {
                        if (x.getPolnocne().getDokad() == y)
                            a = 0;
                    }
                    if (x.getWschodnie() != null)
                    {
                        if (x.getWschodnie().getDokad() == y)
                            a = 1;
                    }
                    if (x.getPoludniowe() != null)
                    {
                        if (x.getPoludniowe().getDokad() == y)
                            a = 2;
                    }
                    if (x.getZachodnie() != null)
                    {
                        if (x.getZachodnie().getDokad() == y)
                            a = 3;
                    }

                    if (a == -1)
                        goto KoniecForeacha;

                    #endregion

                    #region Obliczanie niepewnych kosztów przejścia

                    switch(a){
                        case 0:
                            switch (z)
                            {
                                case 1:
                                case 3:
                                    niepewny_g_score += Program.cennikSwiata.rotateCost;
                                    break;
                                case 2:
                                    niepewny_g_score += 2 * Program.cennikSwiata.rotateCost;
                                    break;
                            }
                            niepewny_g_score += x.getPolnocne().getKosztPrzejscia();
                            break;
                        case 1:
                            switch (z)
                            {
                                case 0:
                                case 2:
                                    niepewny_g_score += Program.cennikSwiata.rotateCost;
                                    break;
                                case 3:
                                    niepewny_g_score += 2 * Program.cennikSwiata.rotateCost;
                                    break;
                            }
                            niepewny_g_score += x.getWschodnie().getKosztPrzejscia();
                            break;
                        case 2:
                            switch (z)
                            {
                                case 1:
                                case 3:
                                    niepewny_g_score += Program.cennikSwiata.rotateCost;
                                    break;
                                case 0:
                                    niepewny_g_score += 2 * Program.cennikSwiata.rotateCost;
                                    break;
                            }
                            niepewny_g_score += x.getPoludniowe().getKosztPrzejscia();
                            break;
                        case 3:
                            switch (z)
                            {
                                case 0:
                                case 2:
                                    niepewny_g_score += Program.cennikSwiata.rotateCost;
                                    break;
                                case 1:
                                    niepewny_g_score += 2 * Program.cennikSwiata.rotateCost;
                                    break;
                            }
                            niepewny_g_score += x.getZachodnie().getKosztPrzejscia();
                            break;
                    }

                    #endregion

                    bool niepewneJestLepsze = false;

                    if (!doRozpatrzeniaPola.Contains(y))
                    {
                        doRozpatrzeniaPola.Add(y);
                        h_score[y] = hKoszt(y, cel);
                        niepewneJestLepsze = true;
                    } else if (niepewny_g_score < g_score[y]) {
                        niepewneJestLepsze = true;
                    }

                    if (niepewneJestLepsze)
                    {
                        came_from[y] = x;
                        g_score[y] = niepewny_g_score;
                        f_score[y] = g_score[y] + h_score[y];
                    }

                    KoniecForeacha:
                    ;
                }
            }

            return null;
        }

        static private PoleMapy znajdzPierwszyKrok(Dictionary<PoleMapy,PoleMapy> came_from, PoleMapy obecnePole)
        {
            PoleMapy wyrzut;
            if(came_from.TryGetValue(came_from[obecnePole], out wyrzut))
            {
                return znajdzPierwszyKrok(came_from, came_from[obecnePole]);
            } else {
                return obecnePole;
            }
        }

        #endregion

    }
}
